package com.snowruin.redis.lock;

import java.nio.charset.Charset;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import com.snowruin.distributed.lock.common.AbstractDisibutedLock;
import com.snowruin.distributed.lock.common.utils.FileUtils;


/**
 * redis  分布式锁的实现
 * @author zxm
 * @version 1.0.0
 * @date 2019-03-01 13:13:00
 * @url https://www.snowruin.com
 *
 */

public class ReidsDistributedLock extends AbstractDisibutedLock {
	
	private final Logger logger = LoggerFactory.getLogger(ReidsDistributedLock.class);
	
	private RedisTemplate<String, Object> redisTemplate;
	
	private ThreadLocal<String> lockFlag = new ThreadLocal<>();

	/**
	 * 释放锁的 脚本
	 */
	private static final String UNLOCK_LUA_SCRIPT = FileUtils.readFileToString("unlock.lua");
	
	/**
	 * 获取锁脚本
	 */
	private static final String LOCK_LUA__SCRIPT = FileUtils.readFileToString("lock.lua");
	
	private static final  Charset UTF8_CHARSET = Charset.forName("UTF-8");
	

	
	/**
	 * 构造函数
	 * @param redisTemplate
	 */
	public ReidsDistributedLock(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}
	
	
	/**
	 * 获取锁
	 */
	@Override
	public boolean lock(String keyName, long expire, int retryTimes, long sleepMillis) {
		// TODO Auto-generated method stub
		boolean result = this.execute(keyName, expire);
		
		try {
			// 如果获取锁失败。则根据 重试次数来进行重试获取锁
			while ( !result && retryTimes -- > 0 ) {
				
				logger.info("未获取到锁，{}毫秒后重试",sleepMillis);
				Thread.sleep(sleepMillis);
				
				// 重新获取锁
				result = this.execute(keyName, expire);
			}
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;
	}
	
	
	private  boolean execute(String keyName , long expire) {
		
		boolean result = redisTemplate.execute((RedisCallback<Boolean>)connection->{
			String flag = UUID.randomUUID().toString().replaceAll("-", "");
			lockFlag.set(flag);
			final byte [] [] keysAndArgs = { keyName.getBytes(UTF8_CHARSET) , flag.getBytes(UTF8_CHARSET) , ((expire/1000)+"").getBytes(UTF8_CHARSET)};
			Long res =  connection.eval(LOCK_LUA__SCRIPT.getBytes(UTF8_CHARSET), ReturnType.fromJavaType(Long.class), 1,keysAndArgs);
			logger.info("执行结果：{}",res);
			
			return res != null && res > 0;
		});
		return result;
	}
	
	/**
	 * 释放锁
	 */
	@Override
	public boolean releaseLock(String keyName) {
		// TODO Auto-generated method stub
		// 释放所得时候，有可能持锁之后方法的执行时间大于锁的有效期 ，此时有可能被另一个现成持有锁 ，所以不能直接删除
		
		// 使用 lua 脚本删除redis 中匹配value的 key ，可以避免由于方法执行时间过长而 redis 锁自动过期失效的时候误删其他线程的锁
		
		try {
			Long result =  redisTemplate.execute((RedisCallback<Long>)connection ->{
				final byte [] [] keysAndArgs = {keyName.getBytes(UTF8_CHARSET) , lockFlag.get().getBytes(UTF8_CHARSET)};
				Long eval = connection.eval(UNLOCK_LUA_SCRIPT.getBytes(UTF8_CHARSET), ReturnType.fromJavaType(Long.class), 1, keysAndArgs);
				return eval;
			});
			
			return  result  != null && result > 0 ;
		} catch (Exception e) {
			logger.info("释放锁的时候发生了异常，",e);
			e.printStackTrace();
		}finally {
			lockFlag.remove();
		}
		return false;
	}

	
	
}
